﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.Networking;


/*
 * ab资源加载
 *  1. 文件加载异步
 *  2. 从Ab中加载UnityEngine资源，异步
 */
namespace Framework
{
    /// <summary>
    /// 负责加载文件，使用webrequest，可以加载对应的资源文件，也可以加载ab资源
    /// ab文件加载之后，有一个预加载的逻辑
    /// </summary>
    public sealed class FileLoader : MonoBehaviour, FileLoaderInterface
    {
        /// <summary>
        /// 同时读取文件的数量限制
        /// </summary>
        private int maxLoadingCount;
        /// <summary>
        /// 当前加载的数量
        /// </summary>
        private int curLoadingCount;
        /// <summary>
        /// 可以失败重试的次数
        /// </summary>
        private int canFailedCount;

        /// <summary>
        /// ab类资源加载换从队列
        /// </summary>
        private Queue<AssetItem> abAssetLoadingTaskQueue;

        /// <summary>
        /// 已经加载了的资源列表
        /// </summary>
        private Dictionary<AssetItem, bool> loadedAbTab;
        /// <summary>
        /// 当前正在加载的ab资源
        /// </summary>
        private AssetItem curLoadingAbAssetItem;
        /// <summary>
        /// 非ab类资源加载缓冲队列
        /// </summary>
        private Dictionary<string, AssetItem> loadingNonAbAssetsTab;

        /// <summary>
        /// 文件加载的缓冲队列
        /// </summary>
        private Queue<AssetItem> fileLoadTaskQueue;

        private bool useUnityCaching = false;
        public void RemoveAsset(AssetItem item)
        {
            // 不需要停止加载
        }

        /// <summary>
        /// 是否使用unity的cache系统
        /// </summary>
        /// <value></value>
        public void SetUseUnityCaching(bool useCache)
        {
            useUnityCaching = useCache;
            Logger.Log($"FileLoader.UseUnityCaching, 开关Unity的cache系统 useUnityCaching = {useUnityCaching} !");
        }
        /// <summary>
        /// 设置同时加载协程的数量
        /// </summary>
        /// <param name="count"></param>
        public void SetMaxLoadingCount(int count)
        {
            maxLoadingCount = count;
        }
        /// <summary>
        /// 设置失败重试次数
        /// </summary>
        /// <param name="retryCount"></param>
        public void SetFailRetryCount(int retryCount)
        {
            canFailedCount = retryCount;
        }

        private bool isPause;
        /// <summary>
        /// 是否暂停加载
        /// </summary>
        /// <value></value>
        public bool IsPause()
        {
            return isPause;
        }

        /// <summary>
        /// 设置暂停
        /// </summary>
        public void SetPause(bool pause)
        {
            isPause = pause;
            if (!isPause)
            {
                StartLoadNewAb();
            }
        }

        public void Init()
        {
            maxLoadingCount = 12;
            curLoadingCount = 0;
            canFailedCount = 2;
            abAssetLoadingTaskQueue = new Queue<AssetItem>();
            loadedAbTab = new Dictionary<AssetItem, bool>();
            loadingNonAbAssetsTab = new Dictionary<string, AssetItem>();
            fileLoadTaskQueue = new Queue<AssetItem>();
        }

        /// <summary>
        /// 加载一个资源，不论是ab还是非ab都走这个接口
        /// </summary>
        /// <param name="assetItem"></param>
        public void Load(AssetItem assetItem)
        {
            //Logger.Log($"FileLoader.Load assetItem = {assetItem} !");
            if (assetItem.ItemType == AssetType.BUNDLE)
            {
                LoadAbFile(assetItem);
                return;
            }
            LoadNonAbFile(assetItem);
        }

        void LoadAbFile(AssetItem item)
        {
            if (curLoadingAbAssetItem == item || abAssetLoadingTaskQueue.Contains(item))
            {
                Logger.Log("资源正在加载种，直接返回 : " + item.AssetUrl);
                return;
            }
            
            abAssetLoadingTaskQueue.Enqueue(item);
            LoadFile(item);
            StartLoadNewAb();
        }

        void LoadNonAbFile(AssetItem item)
        {
            if (loadingNonAbAssetsTab.ContainsKey(item.AssetUrl))
            {
                return;
            }

            loadingNonAbAssetsTab[item.AssetUrl] = item;
            LoadFile(item);
        }

        void LoadFile(AssetItem item)
        {
            if (fileLoadTaskQueue.Contains(item))
            {
                Logger.Log("文件资源正在被加载中，直接返回 : " + item.AssetUrl);
                return;
            }
            
            item.Retain();
            fileLoadTaskQueue.Enqueue(item);
            StartNextFileLoading();
        }

        void StartNextFileLoading()
        {
            while (CanStartNew() && fileLoadTaskQueue.Count > 0)
            {
                var item = fileLoadTaskQueue.Dequeue();
                StartCoroutine(AsyncLoading(item));
            }
        }

        /// <summary>
        /// 加载资源，区分开ab和非ab，ab资源要处理预加载问题
        /// </summary>
        /// <param name="assetItem"></param>
        /// <returns></returns>
        private IEnumerator AsyncLoading(AssetItem assetItem)
        {
            curLoadingCount++;
            if (assetItem.ItemType == AssetType.BUNDLE)
            {
                yield return StartCoroutine(DoAbAsyncLoading(assetItem));
            }else
            {
                yield return StartCoroutine(DoNonAbAsyncLoading(assetItem));
            }
            
            bool isLoadSuccess = string.IsNullOrEmpty(assetItem.fileLoadError);
            if (assetItem.ItemType == AssetType.BUNDLE)
            {
                OnAbFileLoaded(assetItem, isLoadSuccess);
            }
            else
            {
                OnNonAbFileLoaded(assetItem, isLoadSuccess);
            }

            canFailedCount--;
            StartNextFileLoading();
        }

        /// <summary>
        /// 当ab文件加载完成了
        /// </summary>
        /// <param name="assetItem"></param>
        /// <param name="loadSuccess">是否加载成功</param>
        private void OnAbFileLoaded(AssetItem assetItem, bool loadSuccess)
        {
            loadedAbTab.Add(assetItem, loadSuccess);
            StartLoadNewAb();
        }

        /// <summary>
        /// 当从ab中加载引擎资源对象完成后，回调
        /// </summary>
        /// <param name="assetItem">资源</param>
        /// <param name="loadSuccess">是否获取引擎资源对象成功</param>
        private void OnLoadAssetFromAb(AssetItem assetItem, bool loadSuccess)
        {
            OnAbAssetItemLoadDone(assetItem);
        }

        /// <summary>
        /// 当非ab资源加载完成了
        /// </summary>
        /// <param name="assetItem">资源</param>
        /// <param name="loadSuccess">是否加载成功</param>
        private void OnNonAbFileLoaded(AssetItem assetItem, bool loadSuccess)
        {
            assetItem.IsLoadFinish = true;
            loadingNonAbAssetsTab.Remove(assetItem.AssetUrl);
            assetItem.OnLoadedDone();
            assetItem.Release();
            
            if (!loadSuccess)
            {
                ResMgr.Instance.OnLoadedFailed(assetItem);
            }
        }
        
        /// <summary>
        /// 使用自定义的文件流来加载资源
        /// </summary>
        /// <param name="assetItem"></param>
        /// <returns></returns>
        private IEnumerator DoAbAsyncLoading(AssetItem assetItem)
        {
            var rootDir = FrameworkSettings.GetAbRootDir(FrameworkSettings.AndroidPlayer);
            var fullPath = Path.Combine(rootDir, assetItem.AssetUrl);
            fullPath = FileHelper.EnsureDataExt(fullPath);
            fullPath = FileHelper.GetUnityPath(fullPath);
            if (!FileHelper.IsFileExists(fullPath))
            {
                assetItem.fileLoadError = "加载ab文件错误，文件不存在 :"  + fullPath;
                Logger.LogError($"FileLoader.DoAbAsyncLoading_1, load abFile Error : {assetItem.fileLoadError}, assetUrl : {assetItem.AssetUrl}");
                yield break;
            }
            
            // byte key = GetAbEncryptKey(assetItem.encryptAbPath);
            byte key = (byte)1;
            uint bufferSize = 48 * 1024;
            if (assetItem.ResPath.CustomEndsWith(".ttf") || assetItem.ResPath.CustomEndsWith("font"))
            {
                bufferSize = 256 * 1024;
            }
            var myStream = new MyFileStream(fullPath, key, FileMode.Open, FileAccess.Read, FileShare.None, (int)bufferSize, false);
            var assetBundleRequest = AssetBundle.LoadFromStreamAsync(myStream, 0, bufferSize);
            yield return assetBundleRequest;
            
            if (null != assetBundleRequest.assetBundle)
            {
                assetItem.SetAbAssetFromStream(assetBundleRequest.assetBundle);
            }
            else
            {
                assetItem.fileLoadError = "ab加载错误!";
                Logger.LogError($"FileLoader.DoAbAsyncLoading_2, load abFile Error : {assetItem.fileLoadError}, assetUrl : {assetItem.AssetUrl}");
            }
            yield return new WaitForEndOfFrame();
            myStream.Close();
        }

        private static byte GetAbEncryptKey(string md5Name)
        {
            int sum = 0;
            foreach (char item in md5Name)
            {
                sum += (byte)item;
            }
            sum = (sum % 2 == 0) ? (sum + 2) : (sum + 4);
            return (byte)sum;
        }
        
        /// <summary>
        /// 读文件操作，包括读取ab文件和其他非ab文件
        /// </summary>
        /// <param name="assetItem">资源item</param>
        /// <returns></returns>
        private IEnumerator DoNonAbAsyncLoading(AssetItem assetItem)
        {
            int failedTime = 0;
            bool loadSuccess = true;

            while (failedTime < canFailedCount)
            {
                using (UnityWebRequest fileRequest = GetNonAbRequest(assetItem))
                {
                    //设置加载超时为2s
                    fileRequest.timeout = 2;
                    yield return fileRequest.SendWebRequest();
                    
                    //如果是网络错误，则跳出循环
                    if (fileRequest.isHttpError || fileRequest.isNetworkError)
                    {
                        assetItem.fileLoadError = $"文件加载出现网络错误! error = {fileRequest.error}";
                        Logger.LogError($"FileLoader.DoNonAbAsyncLoading_1, load abFile Error : {assetItem.fileLoadError}, assetUrl : {assetItem.AssetUrl}, real Uri {fileRequest.url}");
                        break;
                    }
                    
                    if (fileRequest.isDone)
                    {
                        loadSuccess = assetItem.SetAssetByWebRequest(fileRequest);
                        if (loadSuccess)
                        {
                            yield break;
                        }
                        
                        //加载失败了，等1秒重试
                        Logger.LogError($"FileLoader.DoNonAbAsyncLoading_2, assetItem: {assetItem}  没有加载出来资源对象\nerror: {fileRequest.error}  等待1秒之后重试!, real Uri {fileRequest.url}");
                        yield return new WaitForSeconds(1f);
                    }
                    
                    //加载失败了，清空状态重新开始
                    if (!loadSuccess)
                    {
                        failedTime++;
                    }

                    yield return null;
                }
            }
            
            if (failedTime >= canFailedCount)
            {
                assetItem.fileLoadError = $"重试次数超过了 failedTime = {failedTime}";
                Logger.LogError($"FileLoader.DoNonAbAsyncLoading_3, assetItem: {assetItem} \nerror: {assetItem.fileLoadError} !");
            }
        }

        /// <summary>
        /// 创建非ab资源加载的UnityWebRequest
        /// </summary>
        /// <param name="assetItem">资源缓存单位</param>
        /// <returns></returns>
        private UnityWebRequest GetNonAbRequest(AssetItem assetItem)
        {
            string filePath = assetItem.GetFullPath();
            UnityWebRequest ret = null;
            switch (assetItem.ItemType)
            {
                case AssetType.AUDIO:
                    ret = UnityWebRequestMultimedia.GetAudioClip(filePath, AudioType.MPEG);
                    break;
                case AssetType.DATA:
                case AssetType.TEXT:
                    ret = new UnityWebRequest(filePath);
                    ret.downloadHandler = new DownloadHandlerBuffer();
                    break;
                case AssetType.TEXTURE:
                    ret = UnityWebRequestTexture.GetTexture(filePath);
                    break;
                default:
                    break;
            }
            if (null == ret)
            {
                Logger.LogError($"FileLoader.GetNonAbRequest, assetItem.ItemType {assetItem.ItemType} is not supported!");
            }
            else
            {
                ret.disposeDownloadHandlerOnDispose = true;
            }
            return ret;
        }
        
        /// <summary>
        /// 开始加载新的ab文件,确保乱序加载回来的资源都能正常进入资源缓存中
        /// </summary>
        private void StartLoadNewAb()
        {
            if (isPause)
            {
                return;
            }

            AssetItem assetItem = curLoadingAbAssetItem;
            if (curLoadingAbAssetItem == null && abAssetLoadingTaskQueue.Count > 0)
            {
                assetItem = abAssetLoadingTaskQueue.Dequeue();
                curLoadingAbAssetItem = assetItem;
            }

            if (assetItem == null)
            {
                return;
            }
            
            if (!loadedAbTab.ContainsKey(assetItem))
            {
                // 资源还没加载好，直接return ,等资源加载好了，再回调
                return;
            }
            
            bool loadSuccess = loadedAbTab[assetItem];
            if (loadSuccess && assetItem.NeedLoadAssetFromAb)
            {
                //开始去预加载ab中的资源
                AssetBundleLoader.Instance.PreLoadAssetFromAb(assetItem, OnLoadAssetFromAb);
                loadedAbTab.Remove(assetItem);
                return;
            }

            if (!loadSuccess)
            {
                // 加载失败，移除掉
                ResMgr.Instance.OnLoadedFailed(assetItem);
            }
            
            loadedAbTab.Remove(assetItem);
            OnAbAssetItemLoadDone(assetItem);
        }

        void OnAbAssetItemLoadDone(AssetItem assetItem)
        {
            assetItem.IsLoadFinish = true;
            assetItem.OnLoadedDone();
            assetItem.Release();
            curLoadingAbAssetItem = null;
            StartLoadNewAb();
        }
        
        /// <summary>
        /// 加载ab的任务管道是否空闲
        /// </summary>
        /// <returns></returns>
        private bool AbLoadingIsIdle()
        {
            return (!isPause && (null == curLoadingAbAssetItem) && abAssetLoadingTaskQueue.Count > 0);
        }
        
        /// <summary>
        /// 当前资源是否是在加载过程中
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public bool IsItemInLoading(AssetItem item)
        {
            if (item.ItemType == AssetType.BUNDLE)
            {
                return (abAssetLoadingTaskQueue.Contains(item) || curLoadingAbAssetItem == item);
            }
            return loadingNonAbAssetsTab.ContainsKey(item.AssetUrl);
        }

        /// <summary>
        /// 是否正在加载中
        /// </summary>
        /// <returns></returns>
        public bool IsLoading()
        {
            return (curLoadingCount > 0) || AssetBundleLoader.Instance.IsLoading;
        }
        

        /// <summary>
        /// 是否可以开始新的文件加载
        /// </summary>
        /// <returns></returns>
        private bool CanStartNew()
        {
            if (curLoadingCount < maxLoadingCount)
            {
                return true;
            }
            return false;
        }
    }

    public class MyFileStream : FileStream
    {
        private byte Key;
        public MyFileStream(string path, byte key, FileMode mode, FileAccess access, FileShare share, int bufferSize, bool useAsync)
            : base(path, mode, access, share, bufferSize, useAsync)
        {
            Key = key;
        }

        public MyFileStream(string path, FileMode mode) : base(path, mode)
        {

        }
        public override int Read(byte[] array, int offset, int count)
        {
            // 先不用加密
            // var length = base.Read(array, offset, count);
            // for (int idx = offset; idx < offset + length; idx++)
            // {
            //     array[idx] ^= Key;
            // }
            // return length;
            
            return base.Read(array, offset, count);
        }

        public override void Write(byte[] array, int offset, int count)
        {
            // for (int idx = offset; idx < offset + count; idx++)
            // {
            //     array[idx] ^= Key;
            // }
            
            base.Write(array, offset, count);
        }
    }
}
